C-Dili 15. Konu C ve MS-DOS ile Ekran Duzeni Simdiye kadar kacindigim bir konu ise, ekrani silme, cursor'un yerini ogrenme yada degistirme, ekranin calisma modunu degistirme gibi konular iceren ekran duzenidir. Aslinda C nin bir parcasini olusturmamakla birlikte, bu konu programcilar icin cok onemlidir. C ye sonradan eklenen bir 'uzanti' oldugu, ve sadece MS yada PC-DOS ile calisan bilgisayarlarda kullanilabilecegi icin, burada gorecegimiz int86() fonksiyonu, su anda sadece Microsoft C, ve Turbo C tarafindan desteklenmektedir. Derleyiciniz baska ise, bu fonksiyon cagirilis metodunu degistirmeniz gerekebilir. Cok sayida degisik turde ekran tipleri ile kullanilabilecegi icin, C de tanimlanmis, hazir ekran fonksiyonlari yoktur. Bu fonksiyonlar, kullanilacak cihazin yapisina gore tanimlanabilir. Bu konu icerisinde, elimizdekinin bir IBM-PC yada uyumlu bilgisayar oldugunu kabul edecegiz. Ekrana Nasil Eriselim? Temelde, 3 cesit yoldan ekrana erisebiliriz: 1) bir BIOS interruptu ile, 2) DOS'un ANSI.SYS i ile, 3) Direk donanima 'karisarak'. Her bir metodun avantaj ve dezavantajlari vardir. Daha derine dalmadan once, dilerseniz bu 'interrupt' lafinin manasini cozelim: Interrupt IBM PC ailesi, donanim yada yazilim tarafindan yaratilabilecek interruptlar ile idare edilebilir. Bir interrupt olustugunda, bilgisayarin calismasi, bu interruptu halledebilecek bir rutine yollanir. Bu rutinlerin baslangic adresleri, 'interrupt vektor tablosu'nda saklanir. Bu tablo, bilgisayarin hafizasinin en alt kesiminde, ilk bir kilobytelik yerde bulunur. Bu sahada 255 ayri interrupt icin yer ayrilmistir. Ornegin, 5. interrupt olustugunda, sistem ilk olarak butun registerleri (birazdan anlatacagim) saklar, ve bu ilk 1K lik tablodan, 5. "kutu" ya bakip buradaki adresi okur. Sonra, buradan ogrendigi adrese atlar ve orada ne islemler varsa yapar. Bunlar bitince, tekrar kaldigi isleme geri doner. Donanim Interruptlari (Hardware Interrupts): Bunlar, sistem tarafindan cagirilan rutinlerdir. Ornegin sistem, her saniyede 18.2 kere bir interrupt ile saatini ilerletmektedir. Bu cagirim, yada interrupt, donanim tarafindan yaratilmaktadir. Diger bir baska interrupt ise, 9. klavye interruptudur. Her tusa basildiginda, bu donanim interruptu olusur. Yazilim Interruptlari (Software Interrupts): Bunlar ise, herhangi bir programin cagirabilecegi bir rutinler kutuphanesidir. Ekrana birsey yazmak gerektigine, yada silmek gerektiginde, bunu bir interrupt cagirarak yapariz. BIOS nedir? (BIOS==Basic Input/Output System) BIOS'un gorevi, bilgisayarin yapmasi gereken temel servisleri yerine getirmektir. Genis anlamda, BIOS IBM'in icindeki yongalarda bulunan rutinler kutuphanesidir. BIOS, DOS ile donanim arasinda bir yerde bulunur. Bir taraftan, bir programdan yapilmasi gereken standart BIOS komutunu alir. Programimiz, BIOS a bu istegi, bir interrupt vasitasi ile bildirir. BIOS un diger tarafi ise, bilgisayarin donanim parcalari (ekran, disk drive, seri port, vs.) ile iliski kurar. BIOS'un bu tarafi ise, dikkati cekmek icin bir interrupt yaratan bir donanim ile konusur. DOS nedir? DOS ise, bir baska kutuphanedir. Ozellikle diske erisimde uzmanlasmis olan DOS, bundan baska ekrana yazma, yaziciya bilgi yollama, vs. gibi servisleri de kapsar. DOS'un da ayni BIOS gibi interruptlari ve sagladigi bircok servis vardir. Aslinda DOS, cogu bu servisler icin BIOS'dan yardim gormektedir. Aklinizda bulunsun: BIOS yongalarda bulunur, DOS ise sonradan yuklenir. Simdi, BIOS ile nasil ekran kontrolu yapabilecegimizi gorelim. Bir kere, butun BIOS ekran fonksiyonlari, bir interrupt ile cagirilir, bunun da interrupt numarasi 16 dir. Herhangi bir BIOS fonksiyonunu kullanmak icin yapmamiz gerekenler, once bazi registerleri degistirmek, onaltinci interruptu cagirmak, ve sonuclari zevkle seyretmektir. Register? IBM-PC ailesinin kullandigi 8088 yongasinin, calismasinda kullandigi bazi ozel sahalar vardir. Bu sahalara "register" ismi verilir. IBM-PC de, toplam olarak ondort tane register vardir. PC bunlari, aritmetik islemler, karsilastirmalar gibi islerde kullanir. Bunlardan dort tanesini BIOS interruptlari ile kullanacagiz. Bu kullanacaklarimizin isimleri, AX,BX,CX ve DX dir. Bunlar ayni birer degisken gibi iclerinde degerler tasiyabilirler. Bu registerlerin bir ozelligi ise, ister tam olarak, yani butun AX'i birden, istersek de yarim yarim (AH ve AL yi) degerlerini degistirmemiz mumkundur. Yani, dilersek AX in icine bir 16 bitlik veri koyabiliriz, yada AL ve AH lerin iclerine sekizer bitlik veri koyabiliriz. Hep AX i kullandigima bakmayin, BX i BL ve BH, CX i CH ve CL diye, DX i DL ve DH diye ayirmamiz mumkun. Dilerseniz soyle dusunun: Sayet CX dersek, asagidaki butun yapiyi kastediyoruz: +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ | | | | +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ CH CL Fakat, CH yada CL dersek yarisini kastediyoruz. Yani CX=5 desek, yukaridaki kutulara: +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ | 0 0 0 0 0 0 0 0| | 0 0 0 0 0 1 0 1| +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ CH CL koymus oluruz.. (binary 101, 5 e esittir) Fakat CH=6 desek, +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ | 0 0 0 0 0 1 1 0| | 0 0 0 0 0 1 0 1| +--+--+--+--+--+--+--+--+ +--+--+--+--+--+--+--+--+ CH CL CL nin eski degerine dokunmamis oluruz. Bir onceki ornekte icine '101' koydugumuz icin, CL hala o degerde kaldi. Aslinda butun bunlari bilmemize luzum yok, fakat ileride isinize yarayabilir. Cursor Pozisyonunu Ayarlamak: Dilerseniz ilk olarak ekranin istedigimiz bir yerine atlayip, oraya birseyler yazmayi deneyelim. Bunun icin cursor, yani, bundan sonra yazilacak noktanin degistirilmesi gereklidir. (Cursor, yanip sonen bir alt-cizgi gorumundedir, ve donanim ile kontrol edilir.) POSDEGIS.C: ========================================================= #include void yerlestir(satir,kolon) /* Bu fonksiyon, cursoru istedigimiz */ unsigned satir,kolon; /* bir yere koyar */ { union REGS giris_register,cikis_register; giris_register.h.ah = 2; /* 2: set-cursor-position fonksiyonu*/ giris_register.h.dh = satir; giris_register.h.dl = kolon; giris_register.h.bh = 0; /* hangi sayfayi degistirelim? */ int86(16,&giris_register,&cikis_register); /* cagiralim, yapsin */ } ========================================================== Ilk satirda gordugunuz #include , bu programda sart. Cunku daha sonra gelen 'union' u tanimliyor. Bu union ile, iki tane degisken tanimliyoruz, bunlar giris_register ve cikis_register olarak. Daha sonra, bunlarin gereken elemanlarina verileri atiyoruz. Burada, hangi elemana hangi verinin konacagi, ve servis numarasi (bizde 2) gibi verileri, ya MS-DOS Technical Reference Manual'dan yada Peter Norton'un Programmer's Guide To the IBM-PC den bulabilirsiniz. En son olarak, int86 fonksiyonu ile, 16. interruptu cagiriyoruz. Sistem ilk olarak gidip o hafizanin ilk 1K sindaki tablodan 16. interruptun rutinlerinin baslangic adresini ogreniyor. Daha sonra o adrese atlayip, ikinci fonksiyonunun yerine geciyor, ve register degerlerine gore istedigimizi yerine getiriyor. Ozet: Cursor'u Yerlestirme Fonksiyonu Interrupt no: 16 Servis No: 2 Gereken Veriler: AH=Servis numarasi, yani 2, DH=satir numarasi, DL=kolon numarasi, BH=Kullanilacak Sayfa Numarasi Bu sayfa numarasi parametresini merak edebilirsiniz. Normal bir monokrom ekranin, sadece bir sayfasi vardir. Fakat ornegin CGA (Color Graphics Adaptor), yani renkli adaptoru 'text' yada metin modunda calistiginda, sayet satira-40 karakter modundaysa 8 sayfa, sayet satira-80 karakter modundaysa, 4 sayfayi kullanabilir. Herhangi bir anda bu sayfalardan biri ekranda gosterilebilir. (Evet - sayfa degistirmek icin bir baska fonksiyon cagirmak gerekli.) Bazi programlar bu ozelligi, bir sayfayi kullaniciya gosterirken, bir digerini hazirlayarak, super-hizli ekran goruntuleri saglamakta kullanirlar. Ikinci merak edebileceginiz sey, Cursor Pozisyonunu Okumak olabilir. Bu da, yukaridaki yaziyi anladiysaniz, son derece kolaydir. Bu sefer, interrupt'u cagirdiktan sonra donen degerlerle de ilgilenmekteyiz: POSOGREN.C: ========================================================= #include void posogren(satir, kolon) /* Bu fonksiyon, cursorun yerini BIOS yardimi */ unsigned *satir, *kolon; /* ile ogrenir */ { union REGS giris,cikis; giris.h.ah = 3; /* fonksiyon 3 - cursorun yerini oku */ giris.h.bh = 0; /* 0 inci sayfa.. */ int86(16,&giris,&cikis); *satir = cikis.h.dh; /* 3. fonksiyondan donen degerler: */ *kolon = cikis.h.dl; /* DH icinde, cursorun satir no su, */ } /* ve DL icinde kolon numarasi.. */ ================================================================= Bu programi calistiran ana program, soyle olabilir: main() { int a,b; posogren(&a,&b); printf(" Program calistiginda cursor %d. satir, %d. kolonda idi\n",a,b); } a ve b nin adreslerinin gecirildigine dikkatinizi cekerim. REGS Union'un Parcalari Iki programdir gordugunuz REGS isimli union'un parcalari, bize herhangi bir registerin herhangi bir parcasina erismemizi saglar. Sayet registerin yarisinin degerini degistirmek istiyorsak, yukaridaki gibi, degiskenden sonra 'h' koyariz, giris.h. gibi.. Bundan sonra ise, hangi registerin degismesini istedigimizi soyleriz: giris.h.cl gibi. Sayet bir registerin yarim yarim yerine tumden degistirmek istersek, 'h' yerine 'x' kullanmamiz gerekir: giris.x.bx gibi.. Ekran Tipini Ayarlamak Yazdiginiz program, sadece seksen kolonluk bir ekranda calismak icin duzenlenmis olabilir. Bilmeyen bir kullanici da, ekrani 40 kolona ayarli iken, programi calistirmayi deneyebilir. Bu tip olaylara mani olmak icin, programinizin basinda, ekrani istediginiz tipe ayarlayabilirsiniz. Bunun icin, sifirinci servisi kullanabilirsiniz: EKRANAYA.C: ============================================================= #include ekranayar(tip) /* Bu fonksiyon, ekrani istegimiz tipe ayarlar */ short tip; { union REGS giris,cikis; giris.h.ah = 0; /* 0 inci servis - mod degistirmek */ giris.h.al = tip; /* CGA; 0: b/w text 40x25, 1: 16 renk 40x25 2: b/w text 80x25 3: 16 renk 80x25 4: 4 renk Gra 320x200 5: 4 gri Gra 320x200 6: b/w Gra 640x200 MONO: 7: b/w text 80x25 */ int86(16,&giris,&cikis); /* ayarlayalim */ } ============================================================== Burada, ekranin yeni tipini belirtmemiz gerekli. Bunun icin, 0 ila 15 arasinda bir deger vermemiz gerekiyor. Bu degerlerin 0 ila 6 arasindakiler, CGA (renkli) icin, 7, monokrom icin, ve 8-10 arasi PCJr icin, ve sonrasi EGA icindir. EGA, 8 ve 9 haric diger butun ekran modlarini destekler. Ekrani Silmek Gordunuz bile! Ekrani silmek, iki yoldan olabilir. Birincisi, ekranin modunu degistirmek. Degistirdiginiz mod, su anki mod bile olsa, yine de ekran silinir. Yegane dezavantaj, Compaq tipi makinelerde bu islem uzun zaman alir. Dolayisi ile, bu isi dogru yapmamiz gerekir: EKRANSIL.C: ============================================================== #include void ekransil() /* bu rutin, ekrani siler */ { union REGS gir; gir.h.ah = 6; /* ekrani yukari kaydir: servis no su 6 ekrani asagi kaydir: servis no 7 dir. */ gir.h.al = 0; /* kac satir scroll edecegi 'donecegi' sifir olunca, butun ekrani siler */ gir.h.ch = 0; /* sol ust kosenin satir no su */ gir.h.cl = 0; /* sol ust kosenin kolon no su */ gir.h.dh = 23; /* sag alt kosenin satir no su */ gir.h.dl = 79; /* sag alt kosenin kolon no su */ gir.h.bh = 7; /* yeni yaratilacak satirlar icin renk degeri */ int86(16,&gir,&gir); } ============================================================== Altinci BIOS servisi sayesinde, ekrani yukari kaydirma metodu ile silmekteyiz. Ayni servis sayesinde, CX ve DX de gordugunuz degerleri degistirerek, ekranin sadece bir parcasini 'scroll' etmek yani kaydirmak mumkundur. Kaydirma yonunu servis numarasini 6 yada 7 yaparak degistirebilirsiniz. Burada gordugunu gir.h.bh deki deger ise, yeni acilacak satirlarin 'attribute' yani, rengi ve ozellikleri (parlak, yanip sonen, vs.) dir. Ayrica, yukaridaki ornekte, gir.h.ch = 0; gir.h.cl = 0; yerine, sadece gir.x.cx = 0; diyebilirdik. Baska Interruptlar Bu orneklerde dikkat etmisinizdir - her int86() yi cagirisimizda, ilk parametre olarak 16 yi belirttik. Bu istedigimiz interrupt'un numarasidir. Daha once soyledigim gibi BIOS un ekran fonksiyonlarinin hepsi interrupt 16 ile cagirilir. Fakat tabi, programlarimiz bununla sinirli kalmak zorunda degildir, kullanabilecegimiz daha bircok interrupt vardir. Dilerseniz, su programa bir bakin: PRINTSCR.C: ========================================================== #include main() { union REGS in; /* buna ihtiyacimiz yok, ama tanimlamamiz lazim */ int86(5,&in,&in); /* print-screen yapalim */ } ========================================================== bu program, gorevi ekrani oldugu gibi yaziciya gondermek olan interrupt 5 i kullanmaktadir. Artik klavyeden PRINTSCREEN tusuna bastiginiza, sistemin ne yaptigini biliyorsunuz. DOS ve ANSI.SYS ile Ekran Duzeni Umarim simdi size tumuyle degisik bir ekrana erisme metodu gosterirsem bana kizmassiniz. Bu ikinci metodun birincisi ile neredeyse hicbir alakasi yok. Bu metod sayesinde, programiniz, modem ile bagli uzak bir terminalden calisabilir, DOS'un yonlendirme metodlarindan (TYPE A.TXT > PRN gibi) faydalanabilir. ANSI bir terminali olursa, herhangi bir Unix sisteminde calisabilir. Nasil mi? Cok kolay - yaptigimiz, DOS ekrana birsey gonderirken, ekran idarecisinin anlayabilecegi komutlari kullanmak. Yegane sorun, bu idarecinin siz yukleyinceye kadar calismaz olmasi. Peki, nasil yukleyebiliriz? Sistemi actiginiz diskte, CONFIG.SYS isimli bir kutuk olmasi lazim. Yoksa yaratin, ve icine: DEVICE=ANSI.SYS satirini koyun. Bundan sonra, DOS disketinde bulunan ANSI.SYS isimli kutugun, sistemi actiginiz diskte bulunmasini saglayin. Son olarak da, ANSI.SYS i yuklemek icin, CTRL-ALT-DEL e basin. Alet acildiginda, size fark ettirmeden bu idareci yuklenecektir. Bundan sonra, dilerseniz printf icinde, dilerseniz herhangi baska bir DOS u kullanan rutin ile ANSI yi kullanabilirsiniz. (Anafikir: printf, ekrana yazmak icin, diger bircok C fonksiyonu gibi, DOS'u kullanir.) ANSI ile cursorun yerini degistirmek: Ilk once, butun ANSI komutlari, bir ESC, yani ASCII 27 ile baslarlar. Ornegin, cursorun yerini degistirmek icin, gereken komut ESC [#;#h dir. Ilk # isaretinin yerine satir numarasi, ikincinin yerine de kolon konur. Bu, bir programda soyle gorunebilir: printf("\x1b[%d;%dh",satir,kolon); (Ondalik 27 = Hex 1B ) satiri dikkatle incelerseniz, ilk once \x1b ile ESC karakterini, daha sonra [, sonra ilk rakami, sonra ; ve ikinci rakami, ve son olarak da h isaretini gorebilirsiniz. ANSI nin komut yapisi son derece sabit oldugundan, araya bosluklar katarsaniz, programiniz calismayabilir. ANSI ile Ekrani Silmek Ekrani silmek kolay: ESC [2j yi ekrana yollamaniz yeterli: printf("\x1B[2j"); araya bosluk koymamaya dikkat etmelisiniz. Ayrica kucuk ve buyuk harfler farklidir. Ekranin Rengini Ayarlamak Bunun icin ESC [#;#m komutunu vermeniz gerekiyor. Ilk #, ekrandaki yazilarin rengi, ikincisi ise arka planin rengidir. Bu renk kodlari sunlardir: 30 siyah yazilar 31 kirmizi yazilar 32 yesil yazilar 33 sari yazilar 34 mavi yazilar 35 magenta yazilar (kirmizimsi) 36 cyan yazilar (mavimsi) 37 Beyaz yazilar 40 siyah arka plan 41 kirmizi arka plan 42 yesil arka plan 43 sari arka plan 44 mavi arka plan 45 magenta arka plan 46 cyan arka plan 47 beyaz arka plan Diger ozellikler icin, ESC [#m girmeniz gerekli: # yerine, 0 normal 1 parlak 4 alt cizgi (monokrom da) 5 yanip sonen 7 ters renkler 8 gorunmez DOS mu, BIOS mu kullansak DOS, ve ekran idarecisi ANSI.SYS, BIOS a nazaran daha yavas calisir, fakat bircok ortamda kullanilabileceginden, kalici programlar icin daha uygun bir cozumdur. Ornegin oyunlar gibi yasam sureleri birkac ay olan urunler ise, BIOS yada hatta direk erisim metodlarini kullanabilirler. ANSI nin dezavantaji, kullanicinin ANSI.SYS i yuklemesinin gerektigidir. Bu da, yeni kullanicilari panige kaptiran bir durumdur. Diger Interruptlar Gordugunuz gibi BIOS ile ilgilenirken, sadece 2 interrupt kullandik. Ekran fonksiyonlarini saglayan 16. interrupt, ve ekranin kopyasini yaziciya gonderen 5. interrupt. Bunun gibi daha bircok interrupt vardir, ve bize cok cesitli servisler sunarlar. Ayrica, dilersek kendi interruptlarimizi da yazabiliriz. Bos olarak tanimlanmis bir interrupt vektorunu degistirip, kendi rutinimizin adresini ona verebiliriz. Yada, ornegin sidekick gibi programlarin yaptigi gibi klavyenin yarattigi interrupt sonucunda cagirilan rutini degistirebiliriz, ve diledigimiz baska birseyin yapilmasini saglayabiliriz. Bu tip programlar ilk olarak basilan tusun kendilerini harekete gecirecek tus olup olmadigini kontrol ederler, sayet degilse kontrolu eski interrupt rutinine gecirirler.